<?php 

	abstract class Cache{
		
		/* 
			SMUOI has two temporary directories : 
			
			SYS_TMP_DIR, a subdirectory of SYS_TMP (which you can define yourself though PHP finds this for us using sys_get_temp_dir())
			LOC_TMP_DIR, a subdirectory of LOC_TMP (which you can also define yourself if you like, but MUST live on the same disk partition as SMUOI)
			
			The reason for this is that operations such as copying or gzipping of large files are best done on the same partition
			as the destination directory, in order that we can use PHP's rename() function to swiftly shift the result to their destination.
			In other words, cached data is written to and read from SYS_TMP_DIR, and other files are dealt with in LOC_TMP_DIR
			
			Since the majority of Cache's methods deal with simple cached text and serialized data etc, the default is generally SYS
			
		*/
		
		/* mkbasetmp() : This function uses the UNIX mktemp function to create our destination tmp directories. 
			DEPRECATED
		*/ 
		private function mkbasetmp($T){
			$D = (defined($T.'_TMP')) ? constant($T.'_TMP') : (($T == 'SYS') ? sys_get_temp_dir() :  VAR_DIR.'/tmp');
			$tmpdir = exec("mktemp -d \"$D/smuoitmp.XXXXXXXXXX\"");
			if(!is_dir($tmpdir)) trigger_error("mktemp failed! given dir was $D", E_USER_ERROR);
			chmod($tmpdir, 0755);
			return $tmpdir;
		}
	
		// tmpbase() : this is called before all reads/writes (by checkpath() below), 
		// sets the SYS_TMP_DIR / LOC_TMP_DIR constants and symlinks to self::mktmp($T) if old link unreadable
		// Returns full path to the symlink.
		function tmpbase($L=false){
			$T = ($L) ? 'LOCAL' : 'TMP';
			if(!$C = constant($T.'_DIR')) trigger_error("No constant $T DIR", E_USER_ERROR);
			return $C;
			
		}
		
		// checkpath() : Called before *all* attempts to read or write.  Checks path of file is secure and calls tmpbase. Returns full path.
		function checkpath(&$file, $L=false){
			$file = str_replace('../', '', $file);
			return realpath(self::tmpbase($L))."/$file";
		}
		
		// pathto() : get file location info from relative string.  Now alias of Cache::checkpath
		function pathto($file, $L=false){
			return self::checkpath($file, $L);
		}
		
		function subpath($fp, $L=false){
			$base = self::tmpbase($L);
			if(substr($fp, 0, strlen($base)) !== $base) return false;
			return substr($fp, strlen($base));
		}
		
		// tsinit() : takes a subdir/file of SYS_TMP_DIR or LOC_TMP_DIR as an argument and creates the directory structure if it doesn't yet exist.  
		// Returns full path to dir.
		function tsinit($f, $isfile=false, $L=false){
			$drs = explode('/', $f);
			if($isfile) array_pop($drs);
			$d = self::tmpbase($L);
			//error_log($d);
			foreach($drs as $dr){
				$d .= "/$dr";
				if(!is_dir($d))	mkdir($d, 0755);
			}
			return $d;
		}
	
######  The following functions deal with cached data and only call SYS_TMP_DIR
	
		// mkScratchPad() : creates a temporary file for writing data to before it is saved as a cache file
		private function mkScratchPad(){
			$d = self::tsinit('scratch');
			return exec("mktemp \"$d/XXXXXXXXXX\"");
		}
	
		// writeToCache() : writes $data to $file.  $file must include path below SYS_TMP_DIR
		function write($file, $data, $ser=false){
			$fp = self::checkpath(&$file);
			if(!is_dir(dirname($fp))) self::tsinit($file, 1);
			$scr = self::mkScratchPad();
			$fh = fopen($scr, 'wb');
			fwrite($fh, (($ser) ? serialize($data) : $data));
			fclose($fh);
			exec("mv -f \"$scr\" \"$fp\"");
		}
	
		// get() : reads from cache file. $file must include path below SYS_TMP_DIR
		function get($file, $ser=false){	
			$fp = self::checkpath(&$file);
			if(!is_file($fp)) return null;
			return (($ser) ? @unserialize(@file_get_contents($fp)) : @file_get_contents($fp));
		}
		
		// read() :: alias of Cache::get
		function read($f, $s=false){
			return self::get($f, $s);	
		}
		
		function mktemp($L=true){
			self::clearCache('temps', 3600, $L);
			return self::tsinit('temps/'.utime(), false, $L);	
		}
		
		// tmpSet() : uses filename as dirname and writes data to child file with expiry time as name.
		// This procedure provides the same functionality as calling clearCache before a read except the expiry age is decided during writes, not reads
		function tmpSet($df, $data, $x=3600, $s=true){
			$df = "tmp/$df";
			// clearCache checks path
			self::clearCache($df);
			$x = $x + time();
			self::write("$df/$x", $data, $s);
		}
		
		// tmpGet() : any given tmp directory can only have one child since calls to tmpSet() clears cache. We use a loop anyway
		function tmpGet($df, $s=true){
			$df = "tmp/$df";
			$fp = self::checkpath(&$df);
			if(!is_dir($fp)) return;
			$t = time();
			// this glob loop should give us files in numerical order
			foreach(glob("$fp/*") as $file){
				$x = intval(basename($file));
				if($x > $t) return self::read("$df/$x", $s);
				unlink($file);
			}
			return false;
		}
		
#####	From here down you can call LOC_TMP_DIR if you need to
	
		// deleteFromCache() : deletes a single cache file
		function delete($file, $L=false){
			$fp = self::checkpath(&$file, $L);
			if(is_file($fp)) unlink($fp);
		}
	
		// clearCache() : deletes all files and directories below $d, which must be an existing subdir of SYS_TMP_DIR or LOC_TMP_DIR
		// Optionally can remove only those that are over given $age
		function clearCache($d, $age=0, $L=false, $t=false){
			$fp = self::checkpath(&$d, $L);
			if(!$d || !is_dir($fp)) return;
			if(!$t) $t = time();
			foreach(glob("$fp/*") as $file){
				if(is_dir($file)){
					self::clearCache($d.'/'.basename($file), $age, $L, $t);
					if((!$age)||(filemtime($file) + $age < $t)) @rmdir($file);
				}else{
					if((!$age)||(filemtime($file) + $age < $t)) unlink($file);
				}
			}
		}
		
		function tmpCopy($file, $L=true){
			self::clearCache("copies", 3600, $L);
			if(!is_file($file)) return false;
			$fp = self::tsinit('copies/'.uniqid(), false, $L).'/'.basename($file);
			if(!copy($file, $fp)) return false;
			return $fp;
		}
		
		
	}
	

